﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Xml.XPath;

namespace Allegro.TaskExecution
{
    public abstract partial class Task
    {
        ///<summary>
        ///</summary>
        ///<returns></returns>
        public static Run Run()
        {
            return new Run();
        }
        ///<summary>
        ///</summary>
        ///<param name="name"></param>
        ///<returns></returns>
        public static Run Run(string name)
        {
            return new Run(name);
        }
        ///<summary>
        ///</summary>
        ///<returns></returns>
        public static Run RunInSequence()
        {
            return new Run { InSequence = true, };
        }
        ///<summary>
        ///</summary>
        ///<param name="name"></param>
        ///<returns></returns>
        public static Run RunInSequence(string name)
        {
            return new Run(name) { InSequence = true, };
        }
    }
    ///<summary>
    /// Composite task. Each child task will be executed once, either in sequence or in parallel, depending 
    /// on the setting of InSequence
    ///</summary>
    public class Run : CompositeTask
    {
        /// <summary>
        /// True if the child tasks must be run in sequence, false if not
        /// </summary>
        public bool InSequence { get; set; }

        /// <summary>
        /// Predicate. If not set or evaluates to true, the task will run, otherwise not
        /// </summary>
        public IExecutionCondition If { get; set; }

        ///<summary>
        ///</summary>
        public Run()
        {
            InSequence = true;
        }
        ///<summary>
        ///</summary>
        ///<param name="name"></param>
        public Run(string name)
            : base(name)
        {
            InSequence = true;
        }
        ///<summary>
        ///</summary>
        ///<param name="children"></param>
        public Run(params ITask[] children)
        {
            InSequence = true;
            foreach (ITask t in children)
            {
                AddTask(t);
            }
        }
        ///<summary>
        ///</summary>
        ///<param name="preProcessChildActivityDelegate"></param>
        ///<param name="postProcessChildActivityDelegate"></param>
        public Run(PreProcessChildActivity preProcessChildActivityDelegate, PostProcessChildActivity postProcessChildActivityDelegate)
            : base(preProcessChildActivityDelegate, postProcessChildActivityDelegate)
        {
            InSequence = true;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="outerContext"></param>
        protected override void ExecuteTask(IExecutionContext outerContext)
        {
            int nTasks = Tasks.Count;
            if (nTasks == 0)
                return;
            // Navigate to the task execution context
            IExecutionContext context = GetTaskExecutionContext(this, outerContext);

            // Check the condition
            if (If != null && !If.IsTrue(context))
                return;

            // Reset the completion counter
            context[NumberOfCompletedChildTasks] = 0;

            // Check if the execution should be suspended or aborted
            CheckIfSuspendOrAbort(context);

            ICompositeTaskActivity compositeTaskActivity = Activity as ICompositeTaskActivity;
            if (compositeTaskActivity != null)
            {
                compositeTaskActivity.BeforeExecute(this, context);
            }
            ITaskContextDerivation contextDerive = context as ITaskContextDerivation;
            // Store the child contexts for later use
            Dictionary<ITask, IExecutionContext> childContexts = new Dictionary<ITask, IExecutionContext>(Tasks.Count);

            // Make a copy of the list before enumeration in order to make the enumeration thread safe
            Monitor.Enter(Tasks);
            List<ITask> copiedTaskList = new List<ITask>(Tasks);
            Monitor.Exit(Tasks);

            foreach (ITask task in copiedTaskList)
            {
                IExecutionContext childContext = (contextDerive != null) ? contextDerive.New(task) : context;
                childContexts.Add(task, childContext);
                if (task.Activity != null && compositeTaskActivity != null)
                {
                    compositeTaskActivity.PreProcessChildTask(this, task, context, childContext);
                }
                else if (PreProcessChildActivityDelegate != null)
                {
                    PreProcessChildActivityDelegate(task, context, childContext);
                }
                ITaskContextCoordination taskCoordination = childContext as ITaskContextCoordination;
                if (taskCoordination != null)
                {
                    taskCoordination.RegisterTaskInContext(task);
                }
                if (!InSequence)
                {
                    TaskExecutionRecord taskContextAssociation = new TaskExecutionRecord { Context = childContext, Task = task };
                    ThreadPool.QueueUserWorkItem(RunTask, taskContextAssociation);
                }
                else
                {
                    task.Execute(childContext);

                    if (task.Activity != null && compositeTaskActivity != null)
                    {
                        compositeTaskActivity.PostProcessChildTask(this, task, context, childContexts[task]);
                    }
                    else if (PostProcessChildActivityDelegate != null)
                    {
                        PostProcessChildActivityDelegate(task, context, childContexts[task]);
                    }
                    if (task is ITaskControl && childContext is ITaskContextCoordination)
                    {
                        //ITaskControl control = task as ITaskControl;
                        //control.PostExecute(taskCoordination);
                        taskCoordination.Complete(task);
                    }
                    // Check if the execution should be suspended or aborted
                    CheckIfSuspendOrAbort(context);
                }
            }
            if (!InSequence)
            {
                // Wait for all tasks to complete if running in parallel
                foreach (ITask task in copiedTaskList)
                {
                    ITaskContextCoordination taskContextCoordination =
                            childContexts[task] as ITaskContextCoordination;
                    if (taskContextCoordination != null)
                    {
                        taskContextCoordination.WaitOnTaskCompletion(task);
                        if (task.Activity != null && compositeTaskActivity != null)
                        {
                            compositeTaskActivity.PostProcessChildTask(this, task, context, childContexts[task]);
                        }
                        else if (PostProcessChildActivityDelegate != null)
                        {
                            PostProcessChildActivityDelegate(task, context, childContexts[task]);
                        }
                    }
                }
            }
            if (compositeTaskActivity != null)
            {
                compositeTaskActivity.AfterExecute(this, context);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="persistenceContext"></param>
        protected override void SaveAttributes(System.Xml.XmlWriter writer, IXmlPersistenceContext persistenceContext)
        {
            base.SaveAttributes(writer, persistenceContext);

            // sequential
            if(!InSequence)
                writer.WriteAttributeString("sequential", "false");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="persistenceContext"></param>
        protected override void SaveContent(System.Xml.XmlWriter writer, IXmlPersistenceContext persistenceContext)
        {
            base.SaveContent(writer, persistenceContext);

            // If
            if (If != null)
            {
                writer.WriteStartElement("If", XmlNamespace);
                XmlPersist.SaveObject(writer, If, persistenceContext);
                writer.WriteEndElement();
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="itemNode"></param>
        /// <param name="persistenceContext"></param>
        protected override void LoadAttributes(System.Xml.XPath.XPathNavigator itemNode, IXmlPersistenceContext persistenceContext)
        {
            base.LoadAttributes(itemNode, persistenceContext);

            string sequential = itemNode.GetAttribute("sequential", "");
            if(sequential == "false")
                InSequence = false;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="itemNode"></param>
        /// <param name="persistenceContext"></param>
        protected override void LoadContent(System.Xml.XPath.XPathNavigator itemNode, IXmlPersistenceContext persistenceContext)
        {
            base.LoadContent(itemNode, persistenceContext);

            // If
            XPathNavigator ifNode = itemNode.SelectSingleNode("al:If", persistenceContext.NamespaceManager);
            if (ifNode != null)
            {
                ifNode.MoveToFirstChild();
                If = XmlPersist.LoadObject<IExecutionCondition>(ifNode, persistenceContext);
            }
        }
    }
}
